package com.minivision.fdiot.mqtt.serialize.protobuf;

import java.lang.reflect.Method;
import java.util.concurrent.Callable;

import com.minivision.fdiot.protopool.CodecException;
import com.minivision.fdiot.protopool.BinaryBody;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

/**
 * ClassName: ProtobufCodec <br/>
 * Function: protobuf序列化器，利用反射缓存<tt>method</tt>来进行调用
 */
@Getter
@Setter
@ToString
public abstract class ProtobufBody<T> extends BinaryBody {

  private static final long serialVersionUID = 147116188162950418L;

  /**
   * Protobuf生成原生Java代码中的方法解码方法名称
   */
  private static final String METHOD_NAME_PARSEFROM = "parseFrom";

  /**
   * Protobuf生成原生Java代码中的方法编码方法名称
   */
  private static final String METHOD_NAME_TOBYTE = "toByteArray";

  /**
   * 方法缓存，用于Protobuf生成原生Java代码中的某些编解码方法。 缓存的方法包括:
   * <p/>
   * <ul>
   * <li><code>parseFrom(byte[] bytes)</code></li>
   * <li><code>toByteArray()</code></li>
   * </ul>
   */
  private static final Computable<String, Method> PROTOBUF_METHOD_CACHE = new ConcurrentCache<String, Method>();

  protected T data;

  @Override
  public BinaryBody decode(byte[] data) throws CodecException {
    Class<T> clazz = getType();
    try {
      Method m = PROTOBUF_METHOD_CACHE.get(clazz.getName() + METHOD_NAME_PARSEFROM,
          new Callable<Method>() {
        @Override
        public Method call() throws Exception {
          return clazz.getMethod(METHOD_NAME_PARSEFROM, byte[].class);
        }
      });
      this.data = (T) m.invoke(clazz, data);
      return this;
    } catch (Exception e) {
      throw new CodecException("Decode failed due to " + e.getMessage(), e);
    }
  }

  @Override
  public byte[] encode() throws CodecException {
    if (data == null) {
      return new byte[0];
    }
    Class<T> clazz = getType();
    try {
      Method m = PROTOBUF_METHOD_CACHE.get(clazz.getName() + METHOD_NAME_TOBYTE,
          new Callable<Method>() {
        @Override
        public Method call() throws Exception {
          return clazz.getMethod(METHOD_NAME_TOBYTE);
        }
      });
      byte[] bytes = (byte[]) m.invoke(data);
      return bytes;
    } catch (Exception e) {
      throw new CodecException("Encode failed due to " + e.getMessage(), e);
    }
  }

  public abstract Class<T> getType();

}
